use c5tools::async_crate::tokio::{
  fs::{self, File},
  io::AsyncReadExt,
};
use c5tools::common_crate::{
  anyhow::Result,
  serde_json::{self, json, Value},
};

use std::{collections::HashMap, fs::Metadata, sync::Arc}; //, fmt::Display

use c5tools::web_crate::axum::{
  extract::{Query, State},
  response::Json,
  routing::get,
  Router,
};

use c5tools::{base16, context::AppContext, json_helper::JsonHelper};

//Read file to String, remove BOM if exists
pub async fn get_file(path: &str) -> Result<String> {
  let mut file = File::open(path).await?;
  let mut buffer: Vec<u8> = Vec::new();
  let size = file.read_to_end(&mut buffer).await?;
  let buffer = if size > 3 && buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf {
    &buffer[3..]
  } else {
    &buffer[..]
  };
  let result = String::from_utf8_lossy(buffer).to_string();
  Ok(result)
}

use futures_util::{future::BoxFuture, FutureExt};
pub fn get_dir_file_names(
  path: &str, p: fn(meta: Metadata, file_name: &str) -> bool,
) -> BoxFuture<Result<Vec<(String, String)>>> {
  async move {
    let mut results: Vec<(String, String)> = Vec::new();
    let mut entries = fs::read_dir(&path).await?;
    while let Some(entry) = entries.next_entry().await? {
      let meta = entry.metadata().await?;
      let file_name = entry.file_name();
      let file_name: String = file_name.to_str().unwrap().into();
      let full_name = String::from(path) + "/" + &file_name;
      if p(meta, &file_name) {
        results.push((file_name, full_name));
      }
    }
    Ok(results)
  }
  .boxed()
}

pub(crate) fn api(ctx: Arc<AppContext>, _is_https: bool) -> Router {
  Router::new().route("/ask", get(ask)).with_state(ctx)
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Default)]
struct Srt {
  id: u32,
  time: String,
  text1: String,
  text2: String,
}

pub(crate) async fn ask(
  State(context): State<Arc<AppContext>>, Query(qs): Query<HashMap<String, String>>,
) -> Json<Value> {
  let config = context.config["config"].clone();
  let srt_root = config["srt_root"].str("");
  let empty = String::default();
  let ask = qs.get("ask").unwrap_or(&empty);
  let params = base16::base16_decode(qs.get("params").unwrap_or(&empty)).unwrap();
  let params: Value = serde_json::from_str(&params).unwrap();
  let result = match ask.as_str() {
    "getSeasons" => {
      let list = get_dir_file_names(srt_root, |m, _f| m.is_dir())
        .await
        .unwrap()
        .into_iter()
        .map(|x| x.0)
        .collect::<Vec<_>>();
      json!({
       "title":config["srt_title"].str(""),
       "list": list
      })
    }
    "getEpisodes" => {
      let season = params["season"].str("");
      let path = String::from(srt_root) + "/" + season;
      let list = get_dir_file_names(&path, |m, f| {
        let f = f.to_lowercase();
        m.is_file() && f.contains(".srt") && !f.contains(".eng.srt") && !f.contains("-en.srt")
      })
      .await
      .unwrap()
      .into_iter()
      .map(|x| x.0)
      .collect::<Vec<_>>();
      json!({
        "season":season,
        "list":list})
    }
    "getSrts" => {
      let season = params["season"].str("");
      let file_name = params["episode"].str("");
      let path = String::from(srt_root) + "/" + season + "/" + file_name;
      let text = get_file(&path).await.unwrap();
      let mut lines = text.split("\r\n");
      let mut list: Vec<Srt> = Vec::new();
      //info!(target: "srt_reader","season={},file_name={}", season, file_name);
      while let Some(line) = lines.next() {
        if let Ok(id) = line.parse::<u32>() {
          let mut srt = Srt {
            id,
            ..Default::default()
          };
          if let Some(line) = lines.next() {
            srt.time = line.split(',').next().unwrap_or_default().into();
            if let Some(line) = lines.next() {
              if !line.is_empty() && (id > 0 || !line.contains("QQ")) {
                srt.text1 = line.into();
                if let Some(line) = lines.next() {
                  srt.text2 = line.into();
                  list.push(srt);
                }
              }
            }
          }
        }
      }

      json!({
        "season":season,
        "episode":file_name,
        "list":list})
    }
    _ => Value::Null,
  };
  Json(result)
}
